package com.tang.netty.nio.network;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

/**
 * Server write
 *
 * @author tang
 * @since 2023/8/2 23:14
 */
public class WriteServer {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false); // 非阻塞模式

        ssc.bind(new InetSocketAddress("localhost", 8080));
        Selector selector = Selector.open();

        ByteBuffer allocate = ByteBuffer.allocate(16);
        ssc.register(selector, SelectionKey.OP_ACCEPT, allocate);
        while (true) {
            selector.select(); // 有事发生才向下运行
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) { // 如果是连接事件
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);

                    // 1. 向客户端发送大量数据
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < 5000000; i++) {
                        sb.append("a");
                    }

                    // 创建一个ByteBuffer用于传输
                    ByteBuffer buffer = StandardCharsets.UTF_8.encode(sb.toString());
                    /*
                     * 2. write方法不能保证，一次把所有数据都发送到客户端
                     * write方法的返回值表示实际写入的字节数，如果还有数据要写，我们就继续去写
                     * 先写一次
                     */
                    int write = sc.write(buffer);
                    System.out.println(write);

                    // 3. 判断是否有剩余内容
                    if (buffer.hasRemaining()) {
                        /*
                         * 4. 有剩余待写的内容，那么给给客户端这个channel的selectKey，关注可写事件
                         * 如果只关注读写可能会有问题，也许这个scKey原来不止关注过一种事件
                         * 我们把原来的参数带上就好了 1 只关注读， 4 只关注写
                         * 用加或者位运算都可能，为啥呢？
                         */
                        scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);
//                        scKey.interestOps(scKey.interestOps() | SelectionKey.OP_WRITE);
                        /*
                         * 5. 把未写完的数据挂到selectKey上，不然下次写的数据从哪里来呢？
                         * 其实就是前面章节，放入这个channel注册的selectKey的附件中
                         */
                        scKey.attach(buffer);
                    }
                } else if (key.isWritable()) { // 如果是写事件
                    // 这个key就是当前channel的key
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    SocketChannel sc = (SocketChannel) key.channel();
                    int write = sc.write(buffer);
                    System.out.println(write);
                    // 6. 没有数据可写了，我们需要将附件给清理掉，因为消息如果多了，对内存的占用是相当大的。
                    if (!buffer.hasRemaining()) {
                        // 需要清除buffer
                        key.attach(null);
                        // 不需要再关注可写事件
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                    }
                }
            }
        }

    }

}
